﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CommonLib {
    public class Tracer {
        private Screen screen;
        private Scene scene;

        public Tracer(Screen scr, Scene sce) {
            this.screen = scr;
            this.scene = sce;

        }
        public Screen Screen {
            get { return this.screen; }
        }
        public Primitive findNearest(Ray ray, ref double distance) {
            Primitive nearest = null;

            for (int j = 0; j < scene.CountPrimitive(); j++) {
                Primitive tmp = scene.GetPrimitive(j);
                if (tmp.isIntersect(ray, ref distance) == true)
                    nearest = tmp;
            }
            return nearest;
        }
        public Vector3 estimateDiffuse(Ray ray, Primitive nrst, Vector3 bndry) {
            Vector3 coeff = new Vector3();

            for (int j = 0; j < scene.CountPrimitive(); j++) {
                Primitive light = scene.GetPrimitive(j);
                if (light.Light == false)
                    continue;
                Vector3 primNormal = nrst.getNormal(bndry);
                Vector3 reflRay = ray.direction - 2.0 * ((primNormal & ray.direction) * primNormal);
                Vector3 dir2light = - light.getNormal(bndry);
                Ray ray2light = new Ray(bndry + (DoubleTester.Epsilon * dir2light), dir2light);
                double dist2light = Double.MaxValue;
                double shadow = 1.0;

                // shadow
                if (light.isIntersect(ray2light, ref dist2light) == true) {
                    dist2light += DoubleTester.Epsilon;
                    Primitive closer = findNearest(ray2light, ref dist2light);
                    if (closer != light)
                        shadow = 0.0;
                }

                // diffuse
                double arg = (primNormal & dir2light);
                if (0 < arg) {
                    coeff += nrst.Material.diffuse * arg * light.Material.color * shadow;
                }

                // specular
                arg = (dir2light & reflRay);
                if (0 < arg) {
                    coeff += nrst.Material.specular * Math.Pow(arg, 20.0) * light.Material.color * shadow;
                }
            }
            return coeff;
        }
        public Primitive raytrace(Ray ray, ref Vector3 color, ref Vector3 node, int depth) {
            Primitive nearest = null;
            double distance = Double.MaxValue;

            if (depth <= 0)
                return null;

            nearest = findNearest(ray, ref distance);
            if (nearest == null)
                return nearest;
            if (nearest.Light) {
                //color = nearest.Material.color;
                color = new Vector3(1.0, 1.0, 1.0);
                return nearest;
            }

            node = (distance * ray.direction) + ray.position;
            color = nearest.Material.color * estimateDiffuse(ray, nearest, node);

            Vector3 primNormal = nearest.getNormal(node);
            Vector3 reflDirection = ray.direction - 2.0 * ((ray.direction & primNormal) * primNormal);
            Ray reflRay = new Ray(node + (DoubleTester.Epsilon * reflDirection), reflDirection);
            Vector3 reflColor = new Vector3();
            Vector3 reflNode = new Vector3();
            raytrace(reflRay, ref reflColor, ref reflNode, depth - 1);
            color += nearest.Material.reflection * nearest.Material.color * reflColor;
            return nearest;
        }
        public void render() {
            Primitive prim;

            for (int i = 0; i < screen.getSize(); i++) {
                Vector3 color = new Vector3();
                Vector3 node = new Vector3();

                Ray ray = screen.getRay(i);
                prim = raytrace(ray, ref color, ref node, 3);
                screen.setPixel(i, color);
            }
        }
    }
}
